﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using CoreLab.Oracle;
using Common.Utilities;
using Common.Utilities.Dispatcher;

namespace Workflows.Components.TextMining.Clustering
{
    /// <summary>
    /// 
    /// </summary>
    public class DocumentTermListWorker:IWorker
    {
        #region fields
        private Thread _WorkerThread;
        private DocumentTermListInstruction _Instruction;
        private DocumentTermListOutput _Output;
        private Guid _JobTicketID;
        private RunContext _Context;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }
        #endregion

        #region IWorker Members

        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        public ResultBase Output
        {
            get { return _Output; }
        }

        public Type ComponentType
        {
            get { return typeof(DocumentTermListComponent); }
        }

        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }

        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (DocumentTermListInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (DocumentTermListInstruction)request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = Thread.CurrentThread;
            this.GetDocumentList();
        }

        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = new Thread(new ThreadStart(this.GetDocumentList));
            this._WorkerThread.Start();
        }

        public void CancelWork()
        {
            if (this._WorkerThread != null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._JobTicketID, this.GetType(), this._Instruction);
                }
            }

        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void GetDocumentList()
        {
            IDbConnection conn;
            if (this._Instruction.DbProvider == DataProviderType.MSSQL)
                conn = new SqlConnection(this._Instruction.ConnectionString);
            else
                conn = new OracleConnection(this._Instruction.ConnectionString);
            try
            {
                // doc id -> (term id -> doctermfreq)
                Dictionary<int, Dictionary<int, DocTermFreq>> docTerms = new Dictionary<int, Dictionary<int, DocTermFreq>>();
                Dictionary<int,string> docLabels=new Dictionary<int, string>();
                List<int> excludedTermIDs = new List<int>();
                if (this._Instruction.ExcludedTerms != null && this._Instruction.ExcludedTerms.Count > 0)
                {
                    for (int i = 0; i < this._Instruction.ExcludedTerms.Count; i++)
                    {
                        this._Instruction.ExcludedTerms[i] = this._Instruction.ExcludedTerms[i].ToLower();
                    }
                }
                conn.Open();
                IDbCommand cmd = conn.CreateCommand();
                cmd.CommandType = CommandType.Text;
                IDataReader reader;
                bool useApplListFile = false;
                if(this._Instruction.SelectSqlStatement.IndexOf("{0}")>0 && 
                    (!string.IsNullOrEmpty(this._Instruction.ApplListFilePath)))
                {
                    List<int> applIDs = DocumentTermListWorker.GetApplIDsFromFile(this._Instruction.ApplListFilePath);
                    if(applIDs.Count>0)
                    {
                        useApplListFile = true;
                        foreach(int applID in applIDs)
                        {
                            string sql = this._Instruction.SelectSqlStatement.Replace("{0}", applID.ToString());
                            cmd.CommandText = sql;
                            reader = cmd.ExecuteReader();
                            while(reader.Read())
                            {
                                int docID = int.Parse(reader[this._Instruction.DocIDField].ToString());
                                int termID = int.Parse(reader[this._Instruction.TermIDField].ToString());
                                string term = (string)reader[this._Instruction.TermField];
                                if (!string.IsNullOrEmpty(term) && this._Instruction.ExcludedTerms != null && this._Instruction.ExcludedTerms.Count > 0)
                                {
                                    if (this._Instruction.ExcludedTerms.Contains(term.ToLower()))
                                    {
                                        if (!excludedTermIDs.Contains(termID))
                                        {
                                            excludedTermIDs.Add(termID);
                                        }
                                    }
                                }
                                double weight = 1.0;
                                if (!string.IsNullOrEmpty(this._Instruction.TermWeightField))
                                {
                                    weight = double.Parse(reader[this._Instruction.TermWeightField].ToString());
                                }
                                string docLabel = string.Empty;
                                if (!string.IsNullOrEmpty(this._Instruction.DocLabelField))
                                {
                                    docLabel = reader[this._Instruction.DocLabelField].ToString();
                                }
                                if (!docLabels.ContainsKey(docID))
                                {
                                    docLabels.Add(docID, docLabel);
                                }
                                if (docTerms.ContainsKey(docID))
                                {
                                    Dictionary<int, DocTermFreq> terms = docTerms[docID];
                                    if (terms.ContainsKey(termID))
                                    {
                                        DocTermFreq docTermFreq = terms[termID];
                                        docTermFreq.Count += 1;
                                        terms[termID] = docTermFreq;
                                        docTermFreq.Weight = weight;
                                    }
                                    else
                                    {
                                        DocTermFreq docTermFreq = new DocTermFreq();
                                        docTermFreq.TermID = termID;
                                        docTermFreq.Phrase = term;
                                        docTermFreq.Count = 1;
                                        docTermFreq.Weight = weight;
                                        terms.Add(termID, docTermFreq);
                                    }
                                    docTerms[docID] = terms;
                                }
                                else
                                {
                                    Dictionary<int, DocTermFreq> terms = new Dictionary<int, DocTermFreq>();
                                    DocTermFreq docTermFreq = new DocTermFreq();
                                    docTermFreq.TermID = termID;
                                    docTermFreq.Phrase = term;
                                    docTermFreq.Count = 1;
                                    docTermFreq.Weight = weight;
                                    terms.Add(termID, docTermFreq);
                                    docTerms.Add(docID, terms);
                                }
                            }
                            reader.Close();
                        }
                    }
                }

                if(!useApplListFile)
                {
                    cmd.CommandText = this._Instruction.SelectSqlStatement;
                    reader = cmd.ExecuteReader();

                    while (reader.Read())
                    {
                        int docID = int.Parse(reader[this._Instruction.DocIDField].ToString());
                        int termID = int.Parse(reader[this._Instruction.TermIDField].ToString());
                        string term = (string)reader[this._Instruction.TermField];
                        if (!string.IsNullOrEmpty(term) && this._Instruction.ExcludedTerms != null && this._Instruction.ExcludedTerms.Count > 0)
                        {
                            if (this._Instruction.ExcludedTerms.Contains(term.ToLower()))
                            {
                                if (!excludedTermIDs.Contains(termID))
                                {
                                    excludedTermIDs.Add(termID);
                                }
                            }
                        }
                        double weight = 1.0;
                        if (!string.IsNullOrEmpty(this._Instruction.TermWeightField))
                        {
                            weight = double.Parse(reader[this._Instruction.TermWeightField].ToString());
                        }
                        string docLabel = string.Empty;
                        if (!string.IsNullOrEmpty(this._Instruction.DocLabelField))
                        {
                            docLabel = reader[this._Instruction.DocLabelField].ToString();
                        }
                        if (!docLabels.ContainsKey(docID))
                        {
                            docLabels.Add(docID, docLabel);
                        }
                        if (docTerms.ContainsKey(docID))
                        {
                            Dictionary<int, DocTermFreq> terms = docTerms[docID];
                            if (terms.ContainsKey(termID))
                            {
                                DocTermFreq docTermFreq = terms[termID];
                                docTermFreq.Count += 1;
                                terms[termID] = docTermFreq;
                                docTermFreq.Weight = weight;
                            }
                            else
                            {
                                DocTermFreq docTermFreq = new DocTermFreq();
                                docTermFreq.TermID = termID;
                                docTermFreq.Phrase = term;
                                docTermFreq.Count = 1;
                                docTermFreq.Weight = weight;
                                terms.Add(termID, docTermFreq);
                            }
                            docTerms[docID] = terms;
                        }
                        else
                        {
                            Dictionary<int, DocTermFreq> terms = new Dictionary<int, DocTermFreq>();
                            DocTermFreq docTermFreq = new DocTermFreq();
                            docTermFreq.TermID = termID;
                            docTermFreq.Phrase = term;
                            docTermFreq.Count = 1;
                            docTermFreq.Weight = weight;
                            terms.Add(termID, docTermFreq);
                            docTerms.Add(docID, terms);
                        }
                    }
                    reader.Close();
                }
                

                if (File.Exists(this._Instruction.DocTermListResultFilePath))
                {
                    File.Delete(this._Instruction.DocTermListResultFilePath);
                }
                XmlDocument xDoc = new XmlDocument();
                xDoc.LoadXml("<Docs></Docs>");
                XmlNode root = xDoc.DocumentElement;
                foreach (int docID in docTerms.Keys)
                {
                    XmlNode docNode = XmlDataUtil.AddElement(ref xDoc, root, "Doc");
                    XmlDataUtil.UpdateAttribute(ref xDoc, docNode, "DocID", docID.ToString());
                    string docLabel = string.Empty;
                    if(docLabels.ContainsKey(docID))
                    {
                        docLabel = docLabels[docID];
                    }
                    XmlDataUtil.UpdateAttribute(ref xDoc,docNode,"Label",docLabel);
                    Dictionary<int, DocTermFreq> terms = docTerms[docID];
                    foreach(int termID in terms.Keys)
                    {
                        DocTermFreq docTermFreq = terms[termID];
                        bool add = true;
                        if(excludedTermIDs.Contains(termID))
                        {
                            add = false; 
                        }
                        else
                        {
                            if(this._Instruction.TermWeightHasExclusion)
                            {
                                if(docTermFreq.Weight==this._Instruction.ExcludedTermWeight)
                                    add = false;
                            }
                            if(this._Instruction.TermWeightHasFloor)
                            {
                                if(docTermFreq.Weight < this._Instruction.MinTermWeight)
                                    add = false;
                            }
                            if(this._Instruction.TermWeightHasCeiling)
                            {
                                if(docTermFreq.Weight > this._Instruction.MaxTermWeight)
                                    add = false;
                            }
                        }
                        if (add)
                        {
                            XmlNode termNode = XmlDataUtil.AddElement(ref xDoc, docNode, "Term");

                            XmlDataUtil.UpdateAttribute(ref xDoc, termNode, "TermID", termID.ToString());
                            XmlDataUtil.UpdateAttribute(ref xDoc, termNode, "Phrase", docTermFreq.Phrase);
                            XmlDataUtil.UpdateAttribute(ref xDoc, termNode, "Count", docTermFreq.Count.ToString());
                            XmlDataUtil.UpdateAttribute(ref xDoc, termNode, "Weight", docTermFreq.Weight.ToString());
                        }
                    }

                    if (!string.IsNullOrEmpty(this._Instruction.DocContentField))
                    {
                        string sql = string.Format("select top 1 tmp.{0} from ({1}) tmp where tmp.{2}={3}",
                            this._Instruction.DocContentField,
                            this._Instruction.SelectSqlStatement,
                            this._Instruction.DocIDField,
                            docID);

                        if(useApplListFile)
                        {
                            sql = sql.Replace("{0}", docID.ToString());
                        }

                        cmd.CommandText = sql;
                        reader = cmd.ExecuteReader();
                        if (reader.Read())
                        {
                            if (reader[this._Instruction.DocContentField] != null)
                            {
                                string docContent = (string)reader[this._Instruction.DocContentField];
                                XmlDataUtil.AddCdataElement(ref xDoc, docNode, "Content", docContent);
                            }
                        }
                        reader.Close();
                    }
                }
                xDoc.Save(this._Instruction.DocTermListResultFilePath);

                this._Output = new DocumentTermListOutput(
                    this._Instruction.OwnerComponentID, this._JobTicketID,
                    docTerms.Count, this._Instruction.DocTermListResultFilePath);

                if (this.WorkerFinishedEvent != null)
                {
                    this.WorkerFinishedEvent(this._JobTicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if (this.WorkerErredEvent != null)
                {
                    this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                }
            }
            finally
            {
                conn.Close();
            }
        }
        #endregion

        #region appl list
        /// <summary>
        /// 
        /// </summary>
        /// <param name="applListFilePath"></param>
        /// <returns></returns>
        public static List<int> GetApplIDsFromFile(string applListFilePath)
        {
            List<int> applIDs=new List<int>();
            StreamReader reader = new StreamReader(applListFilePath);
            Regex applIDRegex=new Regex("^(\\d+)\\s+",RegexOptions.IgnoreCase);
            Match match;
            while (!reader.EndOfStream)
            {
                string line = reader.ReadLine();
                if (!string.IsNullOrEmpty(line))
                {
                    match = applIDRegex.Match(line);
                    if(match.Success)
                    {
                        int applID = int.Parse(match.Groups[1].Value);
                        applIDs.Add(applID);
                    }
                }
            }
            reader.Close();
            return applIDs;
        }
        #endregion
    }
}
